package com.zzl.study.auth.security.repository;

import com.zzl.study.auth.constant.RedisConstants;
import com.zzl.study.auth.constant.SecurityConstants;
import com.zzl.study.auth.redis.CommonRedisTemplate;
import com.zzl.study.auth.security.context.SupplierDeferredSecurityContext;
import com.zzl.study.auth.security.authentication.CustomAuthentication;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.*;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.ObjectUtils;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author: zhangzl
 * @date: 2023/12/25 9:49
 * @version: 1.0
 * @description: Redis集中安全向下文存储器
 */
public class RedisSecurityContextRepository implements SecurityContextRepository {

    @Autowired
    private CommonRedisTemplate commonRedisTemplate;

    private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();

    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        throw new UnsupportedOperationException("Method deprecated.");
    }

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        String nonce = getNonce(request);
        if (ObjectUtils.isEmpty(nonce)) {
            return;
        }
        // 如果当前的context是空的，则移除
        SecurityContext emptyContext = this.securityContextHolderStrategy.createEmptyContext();
        if (emptyContext.equals(context)) {
            this.commonRedisTemplate.delete(RedisConstants.SECURITY_CONTEXT_PREFIX_KEY + nonce);
        } else {
            // 保存认证信息
            SecurityContextImpl securityContext = (SecurityContextImpl) context;
            UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) securityContext.getAuthentication();
            CustomAuthentication customAuthentication = CustomAuthentication.reTransform(authentication);
            this.commonRedisTemplate.opsForValue().set(RedisConstants.SECURITY_CONTEXT_PREFIX_KEY + nonce, customAuthentication, RedisConstants.DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
        }
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {
        String nonce = getNonce(request);
        if (ObjectUtils.isEmpty(nonce)) {
            return false;
        }
        // 检验当前请求是否有认证信息
        return Boolean.TRUE.equals(this.commonRedisTemplate.hasKey(RedisConstants.SECURITY_CONTEXT_PREFIX_KEY + nonce));
    }

    @Override
    public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
        Supplier<SecurityContext> supplier = () -> readSecurityContextFromRedis(request);
        return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
    }

    /**
     * 先从请求头中找，找不到去请求参数中找，找不到获取当前session的id
     *
     * @param request 当前请求
     * @return 随机字符串(sessionId)，这个字符串本来是前端生成，现在改为后端获取的sessionId
     */
    private String getNonce(HttpServletRequest request) {
        String nonce = request.getHeader(SecurityConstants.NONCE_HEADER_NAME);
        if (ObjectUtils.isEmpty(nonce)) {
            nonce = request.getParameter(SecurityConstants.NONCE_HEADER_NAME);
            HttpSession session = request.getSession(Boolean.FALSE);
            if (ObjectUtils.isEmpty(nonce) && session != null) {
                nonce = session.getId();
            }
        }
        return nonce;
    }

    /**
     * 从redis中获取认证信息
     *
     * @param request 当前请求
     * @return 认证信息
     */
    private SecurityContext readSecurityContextFromRedis(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        String nonce = getNonce(request);
        if (ObjectUtils.isEmpty(nonce)) {
            return null;
        }
        // 根据缓存id获取认证信息
        CustomAuthentication customAuthentication = (CustomAuthentication) this.commonRedisTemplate.opsForValue().get(RedisConstants.SECURITY_CONTEXT_PREFIX_KEY + nonce);
        if (customAuthentication == null) {
            return null;
        }
        UsernamePasswordAuthenticationToken authentication = customAuthentication.transform();
        return new SecurityContextImpl(authentication);
    }

}
